package marioarena;

import java.util.Date;
import java.util.List;

import org.bson.types.ObjectId;

import com.mongodb.*;

public class DbConnection {
	private final String IP = "localhost";
	private final int PORT = 27017;
	private final String DBNAME = "marioarena";
	private final String GAMESERVERS = "gameservers";
	private final String USERS = "users";

	// 5 seconds until gameservers are removed from the database unless refreshed
	private final int EXPIRATIONTIME = 5000; 

	// collection == table
	// document == row
	// document is inserted into a collection


	/**
	 * Attempts to login.
	 * @param username The username to login with.
	 * @param password The password corresponding with the username.
	 * @return The MongoDB object id of the user if login was successful. Null otherwise.
	 */
	public String login(String username, String password)
	{
		try
		{
			String hashedPassword = Sha1.Hash(password);

			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo(IP, PORT);

			// get database from MongoDB,
			// if database doesn't exists, mongoDB will create it automatically
			DB db = mongo.getDB(DBNAME);

			// Get collection from MongoDB
			// if collection doesn't exists, mongoDB will create it automatically
			DBCollection collection = db.getCollection(USERS);

			// Retrieve the document corresponding with the object Id
			DBObject searchByUsername = new BasicDBObject("username", username);
			DBObject foundDocument = collection.findOne(searchByUsername);
			String foundPassword = (String)foundDocument.get("password");

			if (foundPassword.equals(hashedPassword))
			{
				// Login successful
				String idString = (String)foundDocument.get("_id").toString();
				return idString;
			}
			else 
				return null;
		}
		catch (Exception e)
		{
			return null;
		}
	}

	/**
	 * Inserts a new user into the database if not already exists.
	 * @param username The username to insert.
	 * @param password The password to insert.
	 * @return Null if inserted. Error message otherwise.
	 */
	public String insertUser(String username, String password)
	{
		try
		{
			String hashedPassword = Sha1.Hash(password);

			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo(IP, PORT);

			// get database from MongoDB,
			// if database doesn't exists, mongoDB will create it automatically
			DB db = mongo.getDB(DBNAME);

			// Get collection from MongoDB
			// if collection doesn't exists, mongoDB will create it automatically
			DBCollection collection = db.getCollection(USERS);

			// Check if username already exists
			BasicDBObject criterium = new BasicDBObject("username", username);
			int foundCount = collection.find(criterium).limit(1).size();
			if (foundCount == 0) // Username doesn't exist yet
			{
				System.out.println("Username does not exist yet. Inserting new user.");
				BasicDBObject document = new BasicDBObject();
				document.put("username", username);
				document.put("password", hashedPassword);
				document.put("score", 0);
				collection.insert(document);
			}
			else // Username already exists
			{
				System.out.println("Username already exists!");
				return "Username already exists!";
			} 
			return null;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return "Register user - database error.";
		}
	}

	/**
	 * Updates the score for a player.
	 * @param userID Id of the user of whom the score will change.
	 * @param scoreGained score that will be added to the existing score.
	 */
	public void updateScore(String userId, int scoreGained)
	{
		try
		{
			int currentScore = getScore(userId);
			//only update if score was found.
			if(currentScore != -1){
				int newScore = currentScore + scoreGained;
				
				Mongo mongo = new Mongo(IP, PORT);
				DB db = mongo.getDB(DBNAME);
				DBCollection collection = db.getCollection(USERS);

				// Retrieve the document corresponding with the object Id
				DBObject searchById = new BasicDBObject("_id", new ObjectId(userId));
				DBObject foundDocument = collection.findOne(searchById);

				// Create the update object, adding the fields to be updated
				DBObject updateFields = new BasicDBObject();
				updateFields.put("score", newScore);

				// $Set is used to ensure that only these fields are updated
				DBObject update = new BasicDBObject("$set", updateFields);

				// Execute the update
				collection.findAndModify(foundDocument, update);
				System.out.println("score updated from " + currentScore + " to " + newScore);
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Gets score of player.
	 * @param userId Id of player.
	 * @return Returns score of player.
	 */
	public int getScore(String userId){
		try
		{
			System.out.println("requesting score for " + userId);
			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo(IP, PORT);

			// get database from MongoDB,
			// if database doesn't exists, mongoDB will create it automatically
			DB db = mongo.getDB(DBNAME);

			// Get collection from MongoDB
			// if collection doesn't exists, mongoDB will create it automatically
			DBCollection collection = db.getCollection(USERS);

			// Retrieve the document corresponding with the object Id
			DBObject searchByUserId = new BasicDBObject("_id", new ObjectId(userId));
			DBObject foundDocument = collection.findOne(searchByUserId);
			int score = ((Number)foundDocument.get("score")).intValue();
			System.out.println("Score is " + score);
			return score;
		}
		catch (Exception e)
		{
			System.out.println("Could not get score.");
			return -1;
		}
	}


	/**
	 * Fetches all servers.
	 * @return An HTML string containing all servers.
	 */
	public String fetchAllGameservers()
	{
		String serverListHtml = "";
		try
		{
			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo(IP, PORT);

			// get database from MongoDB,
			// if database doesn't exists, mongoDB will create it automatically
			DB db = mongo.getDB(DBNAME);

			// Get collection from MongoDB
			// if collection doesn't exists, mongoDB will create it automatically
			DBCollection collection = db.getCollection(GAMESERVERS);

			// query all documents
			DBCursor cursor = collection.find();

			// loop over the cursor and display the retrieved result
			while (cursor.hasNext()) {
				//System.out.println(cursor.next());
				BasicDBObject obj = (BasicDBObject) cursor.next();
				String name = obj.getString("name");
				String ip = obj.getString("ip");
				int port = obj.getInt("port");
				int maxPlayers = obj.getInt("maxplayers");
				int currentPlayers = obj.getInt("currentplayers");
				String state = obj.getString("state");
				Date currentTime = (Date)obj.get("timestamp");

				//serverListHtml += "<div class='serverlistRow'><div class='server_name'>" + name + "</div><div class='server_ip'>" + ip + "</div><div class='server_port'>"+ port + "</div><div class='server_maxplayers'>" + maxPlayers + "</div><div class='server_currentplayers'>" + currentPlayers + "</div><div class='server_currenttime'>" + currentTime + "</div></div>";
				serverListHtml += "<div class=\"server-item\" onclick=\"onClickServer(this)\" id=\"0\" serverdata=\"" + name + "#" + ip + "#" + port + "#" + currentPlayers + "#" + maxPlayers + "\">";
				serverListHtml += 
						"<div class=\"server-item-name\">" +
								"<div class=\"server-item-text\">" + name +  "</div>" +
								"</div>" +
								"<div class=\"server-item-ip\">" +
								"<div class=\"server-item-text\">" + ip + "</div>" +
								"</div>" +
								"<div class=\"server-item-port\">" +
								"<div class=\"server-item-text\">" + port + "</div>" +
								"</div>" +
								"<div class=\"server-item-players\">" +
								"<div class=\"server-item-text\">" + currentPlayers + " / " + maxPlayers + "</div>" +
								"</div>" +
								"<div class=\"server-item-state\">" +
								"<div class=\"server-item-text\">" + state + "</div>" +
								"</div>";
				serverListHtml += "</div>"; // Close the first div
			}
		}
		catch (Exception e)
		{
			System.out.println("Could not retrieve active servers.");
			e.printStackTrace();
			return "<div class=\"server-item-name\">Could not retrieve server list.</div>";
		}

		if (serverListHtml.equals(""))
			serverListHtml = "<div class=\"server-item-name\">No servers found.</div>";
		return serverListHtml;
	}

	/**
	 * Inserts a server into the "servers" collection.
	 * @param servername The name of the gameserver.
	 * @param ip The ip-address of the gameserver.
	 * @param port The port of the gameserver.
	 * @param maxPlayers The maximum amount of players allowed in this gameserver.
	 * @param currentPlayers The current amount of players in this gameserver.
	 * @return The id of the inserted server. Null if failed.
	 */
	public String insertServer(String servername, String ip, int port, int maxPlayers, int currentPlayers, String serverState)
	{
		try
		{
			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo(IP, PORT);

			// get database from MongoDB,
			// if database doesn't exists, mongoDB will create it automatically
			DB db = mongo.getDB(DBNAME);

			// Get collection from MongoDB
			// if collection doesn't exists, mongoDB will create it automatically
			DBCollection collection = db.getCollection(GAMESERVERS);

			// create a document to store key and value
			BasicDBObject document = new BasicDBObject();
			document.put("name", servername);
			document.put("ip", ip);
			document.put("port", port);
			document.put("maxplayers", maxPlayers);
			document.put("currentplayers", currentPlayers);
			document.put("state", serverState);

			// Add the current time in java Date format
			Date currentTime = new Date();
			document.put("timestamp", currentTime);

			collection.insert(document);

			String idString = document.get("_id").toString();
			System.out.println(idString);
			return idString;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * Updates a gameserver.
	 * @param objectId The object id of the gameserver to update.
	 * @param currentPlayers The current amount of players of the gameserver to update.
	 */
	public void updateGameserver(String objectId, int currentPlayers, String serverState)
	{
		try
		{
			Date currentTime = new Date();

			Mongo mongo = new Mongo(IP, PORT);
			DB db = mongo.getDB(DBNAME);
			DBCollection collection = db.getCollection(GAMESERVERS);

			// Retrieve the document corresponding with the object Id
			DBObject searchById = new BasicDBObject("_id", new ObjectId(objectId));
			DBObject foundDocument = collection.findOne(searchById);

			// Create the update object, adding the fields to be updated
			DBObject updateFields = new BasicDBObject();
			updateFields.put("currentplayers", currentPlayers);
			updateFields.put("state", serverState);
			updateFields.put("timestamp", currentTime);

			// $Set is used to ensure that only these fields are updated
			DBObject update = new BasicDBObject("$set", updateFields);

			// Execute the update
			collection.findAndModify(foundDocument, update);
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Lets servers expire that haven't been refreshed for a certain amount of time.
	 */
	public void expireGameservers()
	{
		try
		{
			long currentTime = new Date().getTime();

			Mongo mongo = new Mongo(IP, PORT);
			DB db = mongo.getDB(DBNAME);
			DBCollection collection = db.getCollection(GAMESERVERS);

			// query all documents
			DBCursor cursor = collection.find();

			// loop over the cursor and display the retrieved result
			while (cursor.hasNext()) {
				BasicDBObject obj = (BasicDBObject) cursor.next();
				Date timestamp = (Date)obj.get("timestamp");
				long timestampTime = timestamp.getTime();
				long timeDifference = currentTime - timestampTime;
				if (timeDifference >= EXPIRATIONTIME)
				{
					// Remove the gameserver if it has expired (not updated for a period)
					collection.remove(obj);
					
					System.out.println("Gameserver expired.");
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * Drops all databases. Used for debugging purposes.
	 */
	public void dropAllDatabases()
	{
		try
		{
			//connect to mongoDB, ip and port number
			Mongo mongo = new Mongo("localhost", 27017);

			List<String> allDatabases = mongo.getDatabaseNames();
			for (String dbName : allDatabases)
			{
				mongo.dropDatabase(dbName);
			}
		}	
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

}
